package compiler.parser.lalr;

import compiler.grammar.NonTerminal;
import compiler.grammar.Production;
import compiler.grammar.Symbol;
import compiler.grammar.Terminal;
import compiler.parser.lr.LRItem;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/// <summary>
/// Summary description for LALRItem.
/// </summary>
public class LALRItem extends LRItem
{
    //~ Instance fields --------------------------------------------------------

    private LALRItem      tempOriginItem = null;
    private Set<Terminal> lookAhead = new HashSet<Terminal>();
    private Set<LALRItem> propagateItems = new HashSet<LALRItem>();

    //~ Constructors -----------------------------------------------------------

    public LALRItem(Production prod) throws Exception
    {
        super(prod, 0);
    }

    public LALRItem(Production prod, int dot, Set<Terminal> lookahead) throws Exception
    {
        super(prod, dot);
        this.lookAhead = lookahead;
    }

    //~ Methods ----------------------------------------------------------------

    // returns a new lalr_item after shifting the dot, with the same
    // lookahead
    public LALRItem Shift() throws Exception
    {
        if (!super.isDotAtEnd())
        {
            return new LALRItem(getProduction(), getDotPosition() + 1, lookAhead);
        }

        throw new Exception("illegal shift");
    }

    public void addItemToPropagateTo(LALRItem itm)
    {
        propagateItems.add(itm);
    }

    // the follow set is to be computed only when expanding (or when propagating ??)
    // do not compute follow set on shifting nonTerminals !!
    public Set<Terminal> computeFollowSet(Set<Terminal> lookAheadAfter)
    {
        Production    prod = getProduction();

        Set<Terminal> followSet = new HashSet<Terminal>();

        if ((getDotPosition() + 1) <= (prod.getBody().size() - 1))
        {
            for (Symbol symbol : prod.getBody().subList(getDotPosition() + 1, /*exclusive index*/
                                                        prod.getBody().size()))
            {
                if (symbol instanceof Terminal)
                {
                    followSet.add((Terminal)symbol);

                    return followSet;
                }

                followSet.addAll(((NonTerminal)symbol).getFirstSet());

                if (!((NonTerminal)symbol).isNullable())
                {
                    return followSet;
                }
            }
        }

        // we will never reach here unless all symbols after dot are nullable
        followSet.addAll(lookAheadAfter);

        return (followSet);
    }

    public Set<Terminal> getLookAhead()
    {
        return lookAhead;
    }

    public LALRItem getTempOriginItem()
    {
        return tempOriginItem;
    }

    public void propagateLookahead(Set<Terminal> lookaheadItems)
    {
        if ((lookaheadItems.size() == 0) || lookAhead.containsAll(lookaheadItems))
        {
            return;
        }

        lookAhead.addAll(lookaheadItems);

        for (LALRItem itm : propagateItems)
        {
            itm.propagateLookahead(lookaheadItems);
        }
    }

    public void setTempOriginItem(LALRItem tempOriginITem)
    {
        this.tempOriginItem = tempOriginITem;
    }

    public String toHTML()
    {
        StringBuilder str = new StringBuilder();
        str.append("<td align=\"left\">\n");

        if (getProduction().getName() != null)
        {
            String name = getProduction().getName();
            name = name.replace("<", "&lt;");
            name = name.replace(">", "&gt;");
            str.append(name);
        }
        else
        {
            str.append("S'");
        }

        str.append(" = ");

        int i;

        for (i = 0; i < getProduction().getBody().size(); i++)
        {
            if (getDotPosition() == i)
            {
                str.append(".");
            }

            String name = getProduction().getBody().get(i).getName();
            name = name.replace("<", "&lt;");
            name = name.replace(">", "&gt;");
            str.append(name);
        }

        if (getDotPosition() == i)
        {
            str.append(".");
        }

        str.append(" </td><td>\n ");

        List<Terminal> lookaheads = new ArrayList<Terminal>(lookAhead);
        Collections.sort(lookaheads);

        for (Terminal t : lookaheads)
        {
            String name = t.getName();
            name = name.replace("<", "&lt;");
            name = name.replace(">", "&gt;");
            str.append(name + " , ");
        }

        str.append("</td>\n");

        return str.toString();
    }

    @Override
    public String toString()
    {
        StringBuilder str = new StringBuilder();

        if (getProduction().getName() != null)
        {
            str.append(getProduction().getName());
        }
        else
        {
            str.append("S'");
        }

        str.append(" = ");

        int i;

        for (i = 0; i < getProduction().getBody().size(); i++)
        {
            if (getDotPosition() == i)
            {
                str.append(".");
            }

            str.append(getProduction().getBody().get(i).getName());
        }

        if (getDotPosition() == i)
        {
            str.append(".");
        }

        str.append(" ; lookahead: ");

        for (Terminal t : lookAhead)
        {
            str.append(t.getName() + " , ");
        }

        str.append("\n");

        return str.toString();
    }
}
